/*
 * $Id: DefaultFormatter.java 30 2006-06-08 13:26:40Z wjx $
 */
package com.someok.utils.datetime.formatters;


import com.someok.utils.datetime.DateTimeStamp;
import com.someok.utils.datetime.JDateTime;
import com.someok.utils.datetime.JdtFormatter;
import com.someok.utils.datetime.JdtNames;
import com.someok.utils.format.Format;

/**
 * Default formatter used for getting and setting date/time information to
 * and from strings.<p>
 *
 * For setting date and time, default formatter parses input String against
 * specified template. It extracts parts of input string upon patterns
 * and then each part is converted to a number for a date/time information.
 * It doesn't ignore any non-number charater. If conversion fails,
 * <code>null</code> is returned. Translations from String are done by Java.
 * <p>
 *
 * Getting date time is also user firendly. Specified template may not only
 * contains patterns but also any text. To remove errors in decoding when
 * text may be reckognized as one of patterns, template text may be quoted
 * with the ' sign. Double ' quote in the text will be decoded as single
 * quote. <p>
 *
 * Bellow is the list of the patterns that may be used in templates. This
 * list enhances ISO 8601 standard. Patterns notted with + sign are used for
 * settings, all patterns are used for gettings.
 *
 * <ul>
 * 	<li>YYYY + year</li>
 * 	<li>MM + month</li>
 * 	<li>DD + day of month</li>
 * 	<li>D - day of week</li>
 * 	<li>MML - month long name</li>
 * 	<li>MMS - month short name</li>
 * 	<li>DL - day of week long name</li>
 * 	<li>DS - day of week short name</li>
 * 	<li>hh + hour</li>
 * 	<li>mm + minute</li>
 * 	<li>ss + seconds (no milliseconds)</li>
 * 	<li>mss + milliseconds</li>
 * 	<li>ss.mss +  seconds.milliseconds</li>
 * 	<li></li>DDD - day of year</li>
 * 	<li>WW - week of year</li>
 * 	<li>WWW - week of year with 'W' prefix</li>
 * 	<li>W - week of month</li>
 * </ul>
 *
 * <p><a href="DefaultFormatter.java.html"><i>View Source</i></a></p>
 *
 * @version $Revision: 30 $ $Date: 2006-06-08 21:26:40 +0800 (星期四, 08 六月 2006) $
 */
public class DefaultFormatter implements JdtFormatter {

	/**
	 * Array of date/time format patterns according to ISO-8601 standard enhanced
	 * with custom patterns.
	 * <p>
	 *
	 * 'L' suffix is used for long names and 'S' is used for short names text.
	 * <p>
	 *
	 * Warnings:<br>
	 * 1. order of the array elements is IMPORTANT!<br>
	 * 2. the longest match is returned.
	 * 3. patterns that have '+' are used by set(). all patterns are used by get().
	 */
	private final char[][] formc = {
		"YYYY".toCharArray(),		// 0  + year
		"MM".toCharArray(),			// 1  + month
		"DD".toCharArray(),			// 2  + day of month
		"D".toCharArray(),			// 3  - day of week
		"MML".toCharArray(),		// 4  - month long name
		"MMS".toCharArray(),		// 5  - month short name
		"DL".toCharArray(),			// 6  - day of week long name
		"DS".toCharArray(),			// 7  - day of week short name
		"hh".toCharArray(),			// 8  + hour
		"mm".toCharArray(),			// 9  + minute
		"ss".toCharArray(),			// 10 + seconds (no milliseconds)
		"mss".toCharArray(),		// 11 + milliseconds
		"ss.mss".toCharArray(),		// 12 +	seconds.milliseconds
		"DDD".toCharArray(),		// 13 -	day of year
		"WW".toCharArray(),			// 14 - week of year
		"WWW".toCharArray(),		// 15 - week of year with 'W' prefix
		"W".toCharArray(),			// 16 - week of month
	};

	private int findPattern(char[] frmtc, int i) {
		int frmtc_len = frmtc.length;
		boolean match = false;
		int n, lastn = -1;
		int maxLen = 0;
		for (n = 0; n < formc.length; n++) {
			char[] curr = formc[n];					// current pattern from the pattern list
			if (i > frmtc_len - curr.length) {
				continue;
			}
			match = true;
			int delta = 0;
			while (delta < curr.length) {			// match given pattern
				if (curr[delta] != frmtc[i + delta]) {
					match = false;					// no match, goto next
					break;
				}
				delta++;
			}
			if (match == true) {				// match
				if (formc[n].length > maxLen) {			// find longest match
					lastn = n;
					maxLen = formc[n].length;
				}
			}
		}
		return lastn;
	}


	private static final char QUOTE	= '\'';

	/**
	 * Returns date as String in given format. Used by JDateTime for formatting
	 * the string that represents date/time information.
	 *
	 * @param jdt    JDateTime instance
	 * @param frmt   format
	 *
	 * @return date string in given format
	 */
	public String get(JDateTime jdt, String frmt) {
		char[] frmtc = frmt.toCharArray();
		int frmtc_len = frmtc.length;
		StringBuffer result = new StringBuffer(frmtc_len);

		int i = 0;
		while (i < frmtc_len) {

			if (frmtc[i] == QUOTE) {				// quote founded
				int end = i + 1;
				while (end < frmtc_len) {
					if (frmtc[end] == QUOTE) {		// second quote founded
						if (end + 1 < frmtc_len) {
							end++;
							if (frmtc[end] == QUOTE) {	// skip double quotes
								result.append(QUOTE);	// and continue
							} else {
								break;
							}
						}
					} else {
						result.append(frmtc[end]);
					}
					end++;
				}
				i = end;
				continue;			// end of quoted string, continue the main loop
			}

			int n = findPattern(frmtc, i);
			if (n != -1) {									// pattern founded
				JdtNames gds = jdt.getNames();
				switch (n) {
					case 0:
						result.append(Format.sprintf("%~04i", jdt.getYear()));
						break;
					case 1:
						result.append(Format.sprintf("%02i", jdt.getMonth()));
						break;
					case 2:
						result.append(Format.sprintf("%02i", jdt.getDay()));
						break;
					case 3:
						result.append(jdt.getDayOfWeek());
						break;
					case 4:
						result.append(gds.getMonths()[jdt.getMonth() - 1]);
						break;
					case 5:
						result.append(gds.getShortMonths()[jdt.getMonth() - 1]);
						break;
					case 6:
						result.append(gds.getDaysOfWeek()[jdt.getDayOfWeek() - 1]);
						break;
					case 7:
						result.append(gds.getShortDaysOfWeek()[jdt.getDayOfWeek() - 1]);
						break;
					case 8:
						result.append(Format.sprintf("%02d", jdt.getHour()));
						break;
					case 9:
						result.append(Format.sprintf("%02d", jdt.getMinute()));
						break;
					case 10:
						result.append(Format.sprintf("%02d", (int)jdt.getSecond()));
						break;
					case 11:
						result.append(Format.sprintf("%02d", jdt.getMillisecond()));
						break;
					case 12:
						result.append(Format.sprintf("%06.3f", jdt.getSecond()));
						break;
					case 13:
						result.append(Format.sprintf("%03d", jdt.getDayOfYear()));
						break;
					case 14:
						result.append(Format.sprintf("%02d", jdt.getWeekOfYear()));
						break;
					case 15:
						result.append(Format.sprintf("W%02d", jdt.getWeekOfYear()));
						break;
					case 16:
						result.append(jdt.getWeekOfMonth());
						break;
				}
				i += formc[n].length;
			} else {
				result.append(frmtc[i]);
				i++;
			}
		}
		return result.toString();
	}


	/**
	 * Sets time from the string using template. Used by JDateTime for setting
	 * new date/time from the string. But it is also used for validating if some
	 * string represents a valid date.
	 * <p>
	 *
	 * @param s      String that contains time
	 * @param t      template
	 *
	 * @return DateTimeStamp instance or <code>null</code> if error during conversion
	 *         occured
	 */
	public DateTimeStamp set(String s, String t) {
		char[] sc = s.toCharArray();
		char[] tc = t.toCharArray();

		int i = 0;
		int j = 0;
		int slen = s.length();
		int tlen = t.length();

		int year = 0, hour = 0, minute = 0;
		int month = 1, day = 1;
		double second = 0.0;

		while (true) {
			int n = findPattern(tc, i);
			if (n != -1) {					// pattern founded
				i += formc[n].length;
				StringBuffer w = new StringBuffer();
				char next = 0xFFFF;
				if (i < tlen) {
					next = tc[i];			// next = delimeter
				}
				while ((j < slen) && (sc[j] != next)) {
					w.append(sc[j]);
					j++;
				}
				try {
					String ws = w.toString();
					int v = 0;
					double vd = 0;
					if (n != 12) {		// not double
						v = Integer.parseInt(ws);
						if (n == 11) {
							vd = 1.0;
							for (int u = 0; u < ws.length(); u++) {
								vd *= 10.0;
							}
						}
					} else {			// double
						vd = Double.parseDouble(ws);
						vd += 1e-9;
					}
					switch (n) {
						case 0:		year = v; break;
						case 1:		month = v; break;
						case 2:		day = v; break;
						case 8:		hour = v; break;
						case 9:		minute = v; break;
						case 10:	second += v; break;
						case 11:	second += v/vd; break;
						case 12:	second = vd; break;
					}
				} catch (NumberFormatException nfe) {
					return null;
				}
			} else  {
				if (tc[i] == sc[j]) {
					j++;
				}
				i++;
			}
			if ((i == tlen) || (j == slen)) {
				break;
			}
		}
		return new DateTimeStamp(year, month, day, hour, minute, second);
	}
}
